'''
Created on 21/10/2012

@author: Chris
'''
import os
from ttk import Treeview
from Tkinter import Scrollbar, Frame, Listbox, Text, Label, END, SINGLE 
from gitcmd import *
from git import Repo


#=======================================================================================
def autoscroll(sbar, first, last):
#=======================================================================================  
    """
    Hide and show scrollbar as needed.
    Code from Joe English (JE) at http://wiki.tcl.tk/950    
    """
    first, last = float(first), float(last)
    if first <= 0 and last >= 1:
        sbar.grid_remove()
    else:
        sbar.grid()
    sbar.set(first, last)   



#=======================================================================================
class RepositoryBrowser(Frame):
#=======================================================================================
    '''
    Repository Browser
    '''
    
#=======================================================================================
    def __init__(self, master, dirs):
#=======================================================================================
        '''
        dirs: dictionary {dir_name:dir_path}
        '''
        Frame.__init__(self, master)
        self.repository = dirs
        
        self.__column_index = 0
        self.__row_index = 0
        
        # create repository tree view
        self.repotree = self.__create_treeview(True, dirs)
        self.grid(column=0, row=0, sticky='nw')
        # create files tree view
        self.filetree = self.__create_treeview(False)
        self.grid(column=1, row=0, sticky='nw')

        # repository cache repo-name:repo
        self.__repo_cache = {}
        
        # save selected repository
        # definition: repo_name, repo_dir, repo_node, repo_object
        self.selected_repo = ('','', None, None) 
        
        # recording the current selection on file tree
        # its dictionary of file-name: file-properties(type, status, path)
        self.__selected_file = {}

#=======================================================================================
    def save_repo_in_cache(self, repo_name, repo_dir, add_new=False, repo=None):
#=======================================================================================
        '''
        creating repository object is performance consuming, so save repository in cache
        '''
        if not add_new:
            repo = self.__repo_cache[repo_name]
            if repo is not None:
                return
            else:
                repo = Repo(repo_dir)
                self.__repo_cache[repo_name] = repo
        else:
            self.__repo_cache[repo_name] = repo

    
#=======================================================================================
    def __create_treeview(self, isrepository, *args):
#=======================================================================================
        '''
        create tree view for repository and file browser
        '''   
        
        # create a frame for tree view
        frame = Frame(self)
             
        vsb = Scrollbar(frame, orient="vertical")
        hsb = Scrollbar(frame, orient="horizontal")

        # add this tree_view_frame to self
        self.__add_widget(frame)
        
        if isrepository:
            dirs = args[0]
            # create tree for repository
            tree = Treeview(frame, columns="path", displaycolumns="0", show="headings",
                                 yscrollcommand=lambda f,l: autoscroll(vsb, f, l),
                                 xscrollcommand=lambda f,l: autoscroll(hsb, f, l))
            tree.heading('path', text="Repository", anchor='w')
            self.__create_repository_tree(tree, dirs)
            tree.bind('<<TreeviewSelect>>', self.__display_files_in_repo)
            
        else:
            
            # create directory tree view for git files
            tree = Treeview(frame, columns=("name", "type", "status", "path"), displaycolumns=("type", "status"),
                                 yscrollcommand=lambda f,l: autoscroll(vsb, f, l),
                                 xscrollcommand=lambda f,l: autoscroll(hsb, f, l))
            tree.heading('#0', text="Name", anchor='w')
            tree.heading('type', text="Type", anchor='w')
            tree.heading('status', text="Status", anchor='w')
            tree.bind('<<TreeviewSelect>>', self.__display_file_state)

        vsb['command'] = tree.yview
        hsb['command'] = tree.xview
        tree.grid(column=0, row=0, sticky='nswe')
        vsb.grid(column=1, row=0, sticky='ns')
        hsb.grid(column=0, row=1, sticky='ew')
        
        return tree

#=======================================================================================
    def __add_widget(self, frame):
#=======================================================================================
        '''
        add widget to frame
        '''
        frame.grid(column=self.__column_index, row=self.__row_index)
        self.__column_index += 1
        
#=======================================================================================
    def __create_repository_tree(self, tree, dirs):
#=======================================================================================  
        '''
        get directories from controller (main frame) and display them with repository tree view 
        '''
        for dir_name in dirs:
            dir_path = dirs[dir_name]
            node = tree.insert('','end', text=dir_name, values=[dir_name, dir_path])

#=======================================================================================
    def __show_files_in_repo(self, dir_path):
#======================================================================================= 
        '''
        get selected repository in browser and show files in repository with file browser
        '''
        self.filetree.delete(*self.filetree.get_children())
        file_list = os.listdir(dir_path)
        file_type=''
        file_dict = {}
        
        # get repo from current selection
        repo = self.get_selected_repo()   
        # get file status from git repository       
        git_file_property = query_file_status(repo)
        
        for file_name in file_list:
            # do not show .git
            if file_name != ".git":                
                file_path = dir_path+"\\"+file_name
                
                # insert directories into tree make directories display on first rank
                if os.path.isdir(file_path): 
                    file_type = "directory"
                    node = self.filetree.insert('', "end", text=file_name, values=[file_name, file_type])
                    # set the path attribute for directory
                    self.filetree.set(node, "path", file_path)
                    self.__append_nodes_to_tree(self.filetree, node, file_path, git_file_property)
                
                # insert files after directories
                elif os.path.isfile(file_path): 
                    file_dict[file_name] = file_path
        
        # get status of files and display them                    
        file_state = ''
        if git_file_property is None:
            file_state = 'tracked'
                        
            for file_name in file_dict:
                file_path = file_dict[file_name]
                node = self.filetree.insert('', "end", text=file_name, values=[file_name, "file"])  
                # set the status attribute
                self.filetree.set(node, 'status', file_state)
                # set the path attribute for directory
                self.filetree.set(node, "path", file_path)
        else:
            for file_name in file_dict:
                file_path = file_dict[file_name]
                node = self.filetree.insert('', "end", text=file_name, values=[file_name, "file"]) 
                # check file status from git repository
                file_state = get_file_status(file_path, git_file_property)
                # set the status attribute
                self.filetree.set(node, 'status', file_state[0])
                # set the path attribute for directory
                self.filetree.set(node, "path", file_path)

#=======================================================================================
    def __append_nodes_to_tree(self, tree, node, dir_path, git_file_property=None):
#=======================================================================================   
        '''
        add nodes to repository tree
        '''
        file_type = tree.set(node, "type")
        if ("directory" == file_type ):
            tree.delete(*tree.get_children(node))
            
            file_list = os.listdir(dir_path)
            file_dict = {}
            for file_name in file_list:
                # do not show .git
                if file_name == ".git":
                    pass
                else:
                    file_path = dir_path+"\\"+file_name
#                    print "add child node::" + file_path
                    
                    if os.path.isdir(file_path): 
                        file_type = "directory"
                        child_node = tree.insert(node, "end", text=file_name, values=[file_name, file_type])
                        # set the path attribute for directory
                        tree.set(node, "path", file_path)
                        self.__append_nodes_to_tree(tree, child_node, file_path)
                    
                    elif os.path.isfile(file_path): 
                        file_dict[file_name] = file_path
                   
            if git_file_property is None:
                file_state = 'tracked'            
                for file_name in file_dict:
                    file_path = file_dict[file_name]
                    child_node = tree.insert(node, "end", text=file_name, values=[file_name, "file"])
                    # set the status attribute for directory
                    tree.set(child_node, 'status', file_state)
                    # set the path attribute for directory
                    tree.set(child_node, "path", file_path)
            else:
                for file_name in file_dict:
                    file_path = file_dict[file_name]
                    # check file status from git repository
                    file_state = get_file_status(file_path, git_file_property)
                    child_node = tree.insert(node, "end", text=file_name, values=[file_name, "file"])
                    # set the status attribute for directory
                    tree.set(child_node, 'status', file_state[0])
                    # set the path attribute for directory
                    tree.set(child_node, "path", file_path)
#=======================================================================================
    def __display_files_in_repo(self, event):
#=======================================================================================   
        '''
        description: get selected repository and display files under it with file browser
        '''
        tree = event.widget
        node = tree.focus()
        repo_name = tree.set(node, "path")
        repo_dir = self.repository[repo_name]
        
        # if repository object doesn't exists in cache then create and save it in cache
        repo = None
        if repo_name in self.__repo_cache:
            repo = self.__repo_cache[repo_name]
        else:
            repo = Repo(repo_dir)
            
        self.selected_repo = (repo_name, repo_dir, node, repo)
        
        self.__show_files_in_repo(repo_dir)
        
        self.history_broswer.clean_history()

#=======================================================================================
    def __display_file_state(self, event):
#=======================================================================================   
        '''
        display the details of selected file with history browser
        '''
        tree = event.widget
        node = tree.focus()
        
        file_type = tree.set(node, "type")
        if file_type != 'file':
            return
        # only display file in history browser, don't need to handle directory
        
        file_name = tree.set(node, "name")
        file_status = tree.set(node, "status")
        file_path = tree.set(node, "path")
            
        self.__set_selected_file(file_name, file_type, file_status, file_path)
            
        # clean the content of history browser first and then update it
        self.history_broswer.clean_history()
        self.history_broswer.update_history()


#=======================================================================================
    def get_repo_from_cache(self, repo_name, repo_dir=None):
#=======================================================================================
        '''
        get repository object from cache
        '''
        if repo_name in self.__repo_cache:
            return self.__repo_cache[repo_name]
        else:
            repo = Repo(repo_dir)
            self.__repo_cache[repo_name] = repo
            return repo
        
        return None
        

        
        
#=======================================================================================
    def set_history_browser(self, history_broswer):
#=======================================================================================
        self.history_broswer = history_broswer


#=======================================================================================
    def get_selected_repo(self):
#=======================================================================================
        repo_name = self.selected_repo[0]
        
        if repo_name == "" or repo_name is None:
            return None
        
        if repo_name not in self.__repo_cache:
            repo_dir = self.selected_repo[1]
            repo = Repo(repo_dir)
            self.__repo_cache[repo_name] = repo
            return repo
        
        return self.__repo_cache[repo_name]


#=======================================================================================
    def __set_selected_file(self, file_name, file_type, file_status, file_path):
#=======================================================================================
        '''
        save selection of file or directory on file tree
        '''
        
        # only one entry for every selection
        self.__selected_file.clear()
        if file_type == "file":
            self.__selected_file[file_name] = (file_type, file_status, file_path)
        else:
            self.__selected_file[file_name] = (file_type, 'none', file_path)

#=======================================================================================
    def get_selected_file(self):
#=======================================================================================
        
        if len(self.__selected_file) == 0:
            return None
        
        file_name = self.__selected_file.keys()[0]
        
        file_type, file_status, file_path = self.__selected_file[file_name]
        return file_name, file_type, file_status, file_path
    
#=======================================================================================
    def add_new_repo(self, repo_name, repo_dir, repo=None):
#=======================================================================================   
        '''
        add repository into cache and insert corresponding node to repository tree
        '''
        if repo is not None:
            self.save_repo_in_cache(repo_name, repo_dir, True, repo)
        self.repotree.insert('','end', text=repo_name, values=[repo_name, repo_dir])
                   
#=======================================================================================
    def remove_repo(self, repo_dirs):
#=======================================================================================   
        '''
        remove repository object from cache and delete corresponding tree node
        '''
        # get repo_name from selected tree
        repo_name = self.selected_repo[0]

        # delete repository instance in cache
        if repo_name in self.__repo_cache:
            del self.__repo_cache[repo_name]

        # re-build repository tree
        self.repotree.delete(*self.repotree.get_children())
        self.repotree = self.__create_repository_tree(self.repotree, repo_dirs)
        
        # delete corresponding file tree
        self.filetree.delete(*self.filetree.get_children())
        
        # clean file history
        self.history_broswer.clean_history()


#=======================================================================================
    def get_selected_repo_name(self):
#=======================================================================================  
        return self.selected_repo[0]

#=======================================================================================
    def update_files_in_repo(self):
#=======================================================================================   
        '''
        update files in repository
        '''
        dir_path = self.selected_repo[1]
        
        # if there is no select directory, then give up updating
        if dir_path == None or len(dir_path) == 0:
            return
        
        self.__show_files_in_repo(dir_path)


#=======================================================================================
    def is_repo_in_cache(self, repo_name):
#=======================================================================================  
        '''
        check if repository object in cache
        '''
        if repo_name in self.__repo_cache:
            return True
        else:
            return False
        
#=======================================================================================
class HistoryBrowser(Frame):
#=======================================================================================
    '''
    Browser the history of files under control of git
    '''

#=======================================================================================    
    def __init__(self, repo_controller, master=None):
#=======================================================================================
        '''
        dirs: dictionary {dir_name:dir_path}
        '''
        Frame.__init__(self, master)
        
        history_frame = Frame(self, width=50)
        
        vsb = Scrollbar(history_frame, orient="vertical")
        hsb = Scrollbar(history_frame, orient="horizontal")
        vsb.grid(column=1, row=1, sticky='ns')
        hsb.grid(column=0, row=2, sticky='ew')
        
        label1 = Label(history_frame, text="File History")
        label1.grid(column=0,row=0, sticky='nw', pady=5)
        self.file_history_list = Listbox(history_frame)
        self.file_history_list.grid(column=0,row=1, sticky='nw')
        
        
        vsb.config(command=self.file_history_list.yview)
        hsb.config(command=self.file_history_list.xview)
        
        self.file_history_list.config(yscrollcommand=lambda f,l: autoscroll(vsb, f, l))
        self.file_history_list.config(xscrollcommand=lambda f,l: autoscroll(hsb, f, l))

    
        self.file_history_list.bind('<<ListboxSelect>>', self.__update_details)
        history_frame.grid(column=1,row=0, sticky='nw', padx=7)
        
        # init text for displaying details about file history        
        info_frame = Frame(self)
        
        vsb1 = Scrollbar(info_frame, orient="vertical")
        hsb1 = Scrollbar(info_frame, orient="horizontal")
        vsb1.grid(column=1, row=1, sticky='ns')
        hsb1.grid(column=0, row=2, sticky='ew')
        
        label2 = Label(info_frame, text="History Details")
        label2.grid(column=0, row=0, sticky='nw',pady=5)
        
        self.info_text = Text(info_frame,height=10,width=82,background='white')
        self.info_text.grid(column=0, row=1, sticky='nw')
        
        vsb1.config(command=self.info_text.yview)
        hsb1.config(command=self.info_text.xview)
        
        self.info_text.config(yscrollcommand=lambda f,l: autoscroll(vsb1, f, l))
        self.info_text.config(xscrollcommand=lambda f,l: autoscroll(hsb1, f, l))
        
        info_frame.grid(column=0,row=0, sticky='nw', padx=7)
        
        self.controller = repo_controller 
        self.controller.set_history_browser(self)
        
        # save blob and commit objects for current selected file version 
        self.__selected_commit_blob = ()
#=======================================================================================    
    def clean_history(self):
#=======================================================================================
        """clean the content of history list box and text"""
        self.file_history_list.delete(0, END)
        self.info_text.delete(1.0, END)
        
#=======================================================================================    
    def __update_history_listbox(self, file_history):
#=======================================================================================
        self.file_history_list.delete(0, END)
        
        version_index = 1
    
        for key in file_history.iterkeys():
            name = "version-%d"%version_index
            self.file_history_list.insert(END, name)
            version_index += 1
        
#=======================================================================================    
    def update_history(self):
#=======================================================================================
        repo = self.controller.get_selected_repo()
        # if there is no repository selected, give up updating
        if repo == None:
            return
        
        file_property = self.controller.get_selected_file()
        
        if file_property == None:
            return
        
        file_name = file_property[0]
        
        self.file_history = get_file_history_records(file_name, repo)
        self.__update_history_listbox(self.file_history)


#=======================================================================================    
    def __update_details(self, event):
#=======================================================================================
        '''
        show update details in git for selected file
        '''
        self.info_text.delete(1.0, END)
        
        selection = self.file_history_list.curselection()
        if selection == None:
            return
        elif len(selection) == 0:
            return
        
        selected_index = map(int, selection)[0]
        version_index = len(self.file_history) - selected_index - 1
        commit = self.file_history.keys()[version_index]
        
        blob, timestampe, comment = self.file_history.get(commit)
        
        # blob and commit objects
        self.__selected_commit_blob = commit,blob
        
        # display the information on the text widget
        file_details = "update time: " +timestampe + "\n" \
                            + "update comments: " + comment
        self.info_text.insert(END, file_details) 

#=======================================================================================    
    def get_selected_commit_blob(self):
#=======================================================================================
        return self.__selected_commit_blob
    
    
#=======================================================================================    
    def search_file_keywords(self, file_name, keywords, repo):
#=======================================================================================
        '''
        description: search files which have keywords in their comments 
            
        parameter: file_name, the name of target file looked for
        parameter: keywords, keywords in comment
        parameter: repo, the repository object
        return   : blob and commit objects list [(blob, commit)]
        '''
        
        file_history = search_file(file_name, keywords, repo)
        
        
        # update history browser
        if self.file_history != None:
            self.info_text.delete(1.0, END)
            self.__update_history_listbox(file_history)
            